﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using SlimDX;
using SlimDX.Direct3D9;

using System.Windows.Forms;
using System.Runtime.Serialization;
using System.Security.Permissions;
using System.IO;
using System.Xml;
using System.Xml.Serialization;

namespace Splino.D3D
{
    /// <summary>
    /// Defines a non-uniform rational b-spline
    /// </summary>
    [Serializable()]
    public class NURBSObject : SplineObject
    {
        /// <summary>
        /// A list of floats representing a non-uniform knot vector
        /// </summary>
        protected List<float> _knotVector = new List<float>();

        /// <summary>
        /// Gets or sets an array representing the NURBS knot vector for use by XML serialization
        /// </summary>
        [XmlArray("KnotVector")]
        [XmlArrayItem("Knot", typeof(float))]
        public float[] KnotVectorXML
        {
            get { return _knotVector.ToArray(); }
            set
            {
                _knotVector.Clear();
                _knotVector.AddRange(value);
            }
        }

        private Object[] _test = new object[2];
        public Object[] Test
        {
            get { _test[0] = new object();  return _test; }
            set { _test = value; }
        }

        /// <summary>
        /// The polynomial degree of the spline
        /// </summary>
        protected int _degree = 4;
        /// <summary>
        /// Gets or sets the polynomial degree of the spline
        /// </summary>
        public int Degree
        {
            get { return _degree; }
            set
            {
                _degree = value;
                UpdateAllVerticies();
            }
        }

        /// <summary>
        /// Constructor
        /// </summary>
        public NURBSObject() : base() 
        {
            for (int i = 0; i < _degree; i++)
                _knotVector.Add(i);
        }

        /// <summary>
        /// Protected constructor used for creating Point Objects from
        /// clipboard data objects
        /// </summary>
        /// <param name="info">Clipboard Serialization Info</param>
        /// <param name="context">Clipboard Streaming Context</param>
        protected NURBSObject(SerializationInfo info, StreamingContext context)
            : base(info, context) 
        {
            _knotVector = (List<float>)info.GetValue("KnotVector", typeof(List<float>));
            _degree = info.GetInt32("Degree");
            _closed = info.GetBoolean("Closed");
        }

        /// <summary>
        /// Used to serialize a Splino Object and place it in the clipboard
        /// </summary>
        /// <param name="serializationInfo">Clipoard Serialization Info</param>
        /// <param name="streamingContext">Clipboard Streaming Context</param>
        [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
        public override void GetObjectData(SerializationInfo serializationInfo, StreamingContext streamingContext)
        {
            base.GetObjectData(serializationInfo, streamingContext);

            _serializationInfo.AddValue("KnotVector", _knotVector, typeof(List<float>));
            _serializationInfo.AddValue("Degree", _degree, typeof(int));
            _serializationInfo.AddValue("Closed", _closed, typeof(bool));
        }

        /// <summary>
        /// Creates a closed spline with continuous end points
        /// </summary>
        public override void CloseSpline()
        {
            if (_closed)
                return;
            
            PointObject newControlPoint = SceneManager.Instance.Command_Point_MouseDown();

            AppendControlPoint(newControlPoint);

            RemoveEndPoint();

            base.CloseSpline();

            for (int i = 0; i < _degree - 1; i++)
                _knotVector.Add(_knotVector[_knotVector.Count - 1] + 1);
            RemoveEndPoint();
            UpdateAllVerticies();
        }

        /// <summary>
        /// Creates an open ended spline with two endpoints
        /// </summary>
        public override void OpenSpline()
        {
            if (!_closed)
                return;
            
            base.OpenSpline();

            for (int i = 0; i < _degree - 1; i++)
                _knotVector.RemoveAt(_knotVector.Count - 1);

            WeightedPointObject endPoint = new WeightedPointObject();
            endPoint.Center = ((WeightedPointObject)_sceneChildren[0]).Center;
            endPoint.Weight = ((WeightedPointObject)_sceneChildren[0]).Weight;
            AppendControlPoint(endPoint);
            UpdateAllVerticies();
        }

        /// <summary>
        /// Adds an existing Point Object as a new control point to the 
        /// end of the spline
        /// </summary>
        /// <param name="point">The Point Object to append to the end
        /// of the spline</param>
        public override void AppendControlPoint(PointObject point)
        {
            if (_knotVector.Count == 0)
                _knotVector.Add(0f);
            else
                _knotVector.Add(_knotVector[_knotVector.Count - 1] + 1);
            if (point is WeightedPointObject)
                base.AppendControlPoint(point);
            else
            {
                WeightedPointObject weightedPointObject = new WeightedPointObject();
                weightedPointObject.Center = point.Center;
                base.AppendControlPoint(weightedPointObject);
            }
        }

        /// <summary>
        /// Removes the last control point of the spline
        /// </summary>
        public override void RemoveEndPoint()
        {
            _knotVector.RemoveAt(_knotVector.Count - 1);
            base.RemoveEndPoint(); 
        }

        public override void UpdateControlPoint(PointObject controlPoint)
        {
            List<CustomVertex.PositionColored> vertexList = new List<CustomVertex.PositionColored>();
            int controlPointIndex = SceneChildren.IndexOf(controlPoint);
            float start = _knotVector[0];
            if (controlPointIndex > 1)
                start = controlPointIndex;
            float end = _knotVector[_knotVector.Count - 1];
            if (_closed)
            {
                start = _knotVector[_degree - 1];// - _knotVector[0]) / 2f + _knotVector[0];
                end = _knotVector[_knotVector.Count - _degree];// - (_knotVector[_knotVector.Count - 1] - _knotVector[_knotVector.Count - 2]) / 2f;
            }
            for (float i = start; i < end; i += 0.1f)
            {
                CustomVertex.PositionColored vertex = new CustomVertex.PositionColored();
                vertex.Position = Evaluate(i);
                vertex.Color = _currentColor.ToArgb();
                vertexList.Add(vertex);
            }
            vertexList.ToArray().CopyTo(_vertexList, _vertexList.Length - vertexList.Count);
            _vertexCount = _vertexList.Length;

            Dispose();
            ResetBuffers();
        }

        /// <summary>
        /// Returns a Vector3 point on the spline at a specified index
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public override Vector3 Evaluate(float index) 
        {
            Vector3 splinePoint = Vector3.Zero;
            Vector3 numerator = Vector3.Zero;
            float denominator = 0f;
            int endIndex = _sceneChildren.Count;

            if (_closed)
                endIndex += _degree - 1;

            for (int i = 0; i < endIndex; i++)
            {
                int childIndex = i;
                if (childIndex >= _sceneChildren.Count)
                    childIndex -= _sceneChildren.Count;

                WeightedPointObject weightedPoint = (WeightedPointObject)_sceneChildren[childIndex];
                numerator += weightedPoint.Weight * weightedPoint.Center * BasisFunction(index, _degree, i);
                denominator += weightedPoint.Weight * BasisFunction(index, _degree, i);

            }

            if (numerator == Vector3.Zero && denominator == 0)
            {
                if ((int)index < _sceneChildren.Count)
                    return ((WeightedPointObject)_sceneChildren[(int)index]).Center;
                return ((WeightedPointObject)_sceneChildren[0]).Center;
            }

            splinePoint = numerator / denominator;            
            return splinePoint; 
        }

        /// <summary>
        /// Recursive basis function for evaluating the spline
        /// </summary>
        /// <param name="u">The spline index being evaluated</param>
        /// <param name="degree">The polynomial degree</param>
        /// <param name="i">The index of the current control point</param>
        /// <returns></returns>
        protected float BasisFunction(float u, int degree, int i)
        {
            if (degree == 1)
            {
                if (i == _knotVector.Count - 1)
                    return 0;
                if (_knotVector[i] <= u && u < _knotVector[i + 1])
                    return 1;
                return 0;
            }
            float g = ((u - _knotVector[i]) / (_knotVector[i + degree - 1] - _knotVector[i])) * BasisFunction(u, degree - 1, i);
            float h = ((_knotVector[i + degree] - u) / (_knotVector[i + degree] - _knotVector[i + 1])) * BasisFunction(u, degree - 1, i + 1);
            if (float.IsNaN(g))
                g = 0;
            if (float.IsNaN(h))
                h = 0;

            return g + h;
        }

        public static void CreateCurve()
        {
            NURBSObject curve = new NURBSObject();
            WeightedPointObject point1 = new WeightedPointObject();
            WeightedPointObject point2 = new WeightedPointObject();
            WeightedPointObject point3 = new WeightedPointObject();
            WeightedPointObject point4 = new WeightedPointObject();
            WeightedPointObject point5 = new WeightedPointObject();
            WeightedPointObject point6 = new WeightedPointObject();
            WeightedPointObject point7 = new WeightedPointObject();
            WeightedPointObject point8 = new WeightedPointObject();

            point1.Center = new Vector3(.1f, .2f, 0);
            point2.Center = new Vector3( .25f, .2f, 0 );
            point3.Center = new Vector3( .4f, .2f, 0 );
            point4.Center = new Vector3( .5f, .9f, 0);
            point5.Center = new Vector3(.6f, .2f, 0);
            point6.Center = new Vector3(.7f, .5f, 0 );
            point7.Center = new Vector3(.8f, .2f, 0 );
            point8.Center = new Vector3(.9f, .2f, 0 );

            point1.Weight = 1;
            point2.Weight = 1;
            point3.Weight = 1;
            point4.Weight = 3;
            point5.Weight = 1;
            point6.Weight = 1;
            point7.Weight = 1;
            point8.Weight = 1;

            curve.AddSceneChild(point1);
            curve.AddSceneChild(point2);
            curve.AddSceneChild(point3);
            curve.AddSceneChild(point4);
            curve.AddSceneChild(point5);
            curve.AddSceneChild(point6);
            curve.AddSceneChild(point7);
            curve.AddSceneChild(point8);

            curve._degree = 3;

            curve._knotVector.Clear();
            curve._knotVector.Add(0f);
            curve._knotVector.Add(0.001f);
            curve._knotVector.Add(0.002f);
            curve._knotVector.Add(1);
            curve._knotVector.Add(2);
            curve._knotVector.Add(3);
            curve._knotVector.Add(4);
            curve._knotVector.Add(5);
            curve._knotVector.Add(6);
            curve._knotVector.Add(6.001f);
            curve._knotVector.Add(6.002f);

            curve.UpdateAllVerticies();
            SceneManager.Instance.MasterSceneObject.AddSceneChild(curve);

            List<List<double>> n = new List<List<double>>();
            for (int i = 0; i < curve._knotVector.Count - curve._degree; i++)
            {
                n.Add(new List<double>());
                for (float j = 0; j < curve.SceneChildren.Count/2; j += 0.01666f)
                    n[i].Add(curve.BasisFunction(j, curve._degree, i));
            }
        }

        public static void CreateCircle()
        {
            NURBSObject circle = new NURBSObject();
            WeightedPointObject point1 = new WeightedPointObject();
            WeightedPointObject point2 = new WeightedPointObject();
            WeightedPointObject point3 = new WeightedPointObject();
            WeightedPointObject point4 = new WeightedPointObject();
            WeightedPointObject point5 = new WeightedPointObject();
            WeightedPointObject point6 = new WeightedPointObject();
            WeightedPointObject point7 = new WeightedPointObject();
            WeightedPointObject point8 = new WeightedPointObject();
            WeightedPointObject point9 = new WeightedPointObject();

            point1.Center = new Vector3(0f, -1f, 0);
            point2.Center = new Vector3(-1f, -1f, 0);
            point3.Center = new Vector3(-1f, 0f, 0);
            point4.Center = new Vector3(-1f, 1f, 0);
            point5.Center = new Vector3(0f, 1f, 0);
            point6.Center = new Vector3(1f, 1f, 0);
            point7.Center = new Vector3(1f, 0f, 0);
            point8.Center = new Vector3(1f, -1f, 0);
            point9.Center = new Vector3(0f, -1f, 0);

            point1.Weight = 1;
            point2.Weight = (float)Math.Sqrt(2) / 2;
            point3.Weight = 1;
            point4.Weight = (float)Math.Sqrt(2) / 2;
            point5.Weight = 1;
            point6.Weight = (float)Math.Sqrt(2) / 2;
            point7.Weight = 1; 
            point8.Weight = (float)Math.Sqrt(2) / 2;
            point9.Weight = 1;

            circle._sceneChildren.Add(point1);
            circle._sceneChildren.Add(point2);
            circle._sceneChildren.Add(point3);
            circle._sceneChildren.Add(point4);
            circle._sceneChildren.Add(point5);
            circle._sceneChildren.Add(point6);
            circle._sceneChildren.Add(point7);
            circle._sceneChildren.Add(point8);
            circle._sceneChildren.Add(point9);

            circle._knotVector.Clear();
            circle._knotVector.Add(0f);
            circle._knotVector.Add(0.001f);
            circle._knotVector.Add(0.002f);
            circle._knotVector.Add(.25f);
            circle._knotVector.Add(.251f);
            circle._knotVector.Add(.5f);
            circle._knotVector.Add(.51f);
            circle._knotVector.Add(.75f);
            circle._knotVector.Add(.751f);
            circle._knotVector.Add(1f);
            circle._knotVector.Add(1.001f);
            circle._knotVector.Add(1.002f);

            circle._degree = 3;
            circle.UpdateAllVerticies();
            SceneManager.Instance.MasterSceneObject.AddSceneChild(circle);
        }

        /// <summary>
        /// Updates each vertex in the spline from the current position
        /// of each control point
        /// </summary>
        public override void UpdateAllVerticies()
        {
            //Evaluate2();
            List<CustomVertex.PositionColored> vertexList = new List<CustomVertex.PositionColored>();
            float start = _knotVector[0];
            float end = _knotVector[_knotVector.Count - 1];
            if (_closed)
            {
                start = _knotVector[_degree - 1];// - _knotVector[0]) / 2f + _knotVector[0];
                end = _knotVector[_knotVector.Count - _degree];// - (_knotVector[_knotVector.Count - 1] - _knotVector[_knotVector.Count - 2]) / 2f;
            }
            for (float i = start; i < end; i += 0.1f)
            {
                CustomVertex.PositionColored vertex = new CustomVertex.PositionColored();
                vertex.Position = Evaluate(i);
                vertex.Color = _currentColor.ToArgb();
                vertexList.Add(vertex);
            }

            _vertexList = vertexList.ToArray();
            _vertexCount = vertexList.Count;

            Dispose();
            ResetBuffers();
        }

        /// <summary>
        /// Draws the spline object on the screen.
        /// </summary>
        public override void Draw(DrawLayer drawLayer, SplinoViewport splinoViewport)
        {
            base.Draw(drawLayer, splinoViewport);
            if (_drawLayer == drawLayer)
            {
                DeviceManager.Instance.Device.SetStreamSource(
                    0, _vertexBuffer, 0, CustomVertex.PositionColored.SizeInBytes);
                DeviceManager.Instance.Device.DrawPrimitives(
                    PrimitiveType.LineStrip,
                    0,
                    _vertexCount - 1);
            }
        }
    }
}
